<?php
abstract class mode_type_action {
	public $add;
}
class mode_type_user_action extends mode_type_action {
	public	$user = null;
	function __construct(user &$user, $add = true) {
		$this->user =& $user;
		$this->add = $add;
	}
}
class mode_type_wild_action extends mode_type_action {
	public	$wild = '';
	function __construct($wild, $add = true) {
		//todo check $wild
		$this->wild = $wild;
		$this->add = $add;
	}
}
abstract class modes {
	const		type_normal     = 0,
				type_int        = 1,
				type_string     = 2,
				type_array      = 3,
				type_user       = 4,
				type_channel    = 5,
				type_wild       = 6,
				type_flood      = 7,
				type_unknown    = -1;
	public		$parent = null,
				$typeofmode = array();
	protected	$_modes = '',
				$_values = array();
	public function __construct(basic &$parent) {
		if (get_parent_class($this)."_".get_class($parent) != get_class($this))
			throw new Exception("Parent type mismatch");
		$this->parent =& $parent;
		foreach ($this->typeofmode as $mode => $type) {
			switch ($type) {
				case self::type_normal:
					$this->_values[$mode] = false;
					break;
				case self::type_unknown:
				case self::type_channel:
				case self::type_array:
					$this->_values[$mode] = null;
					break;
				case self::type_int:
					$this->_values[$mode] = 0;
					break;
				case self::type_string:
				case self::type_flood:
					$this->_values[$mode] = '';
					break;
				case self::type_user:
					$this->_values[$mode] = array('by_name'=>array());
					break;
				case self::type_wild:
					$this->_values[$mode] = array('by_wild'=>array());
					break;
			}
		}
	}
	protected function __isset($nm) {
		return (strlen($nm) == 2 and ($nm{0} == 'l' or $nm{0} == 'u'));
	}
	protected function __get($nm) {
		if (!isset($this->$nm)) return null;
		$mode = ($nm{0} == 'l' ? strtolower($nm{1}) : strtoupper($nm{1}));
		$type = (!isset($this->typeofmode[$mode]) ? self::type_normal : $this->typeofmode[$mode]);
		if ($type == self::type_user)
			$exists = !empty($this->_values[$mode]['by_name']);
		elseif ($type == self::type_wild)
			$exists = !empty($this->_values[$mode]['by_wild']);
		else 
			$exists = strpos($this->_modes,$mode) !== false;
		return (!$exists ? false : $this->_values[$mode]);
	}
	protected function __set($nm, $val) {
		if (!isset($this->$nm)) return false;
		$mode = ($nm{0} == 'l' ? strtolower($nm{1}) : strtoupper($nm{1}));
		if (!$this->$nm) $this->_modes .= $mode;
		if (!isset($this->typeofmode[$mode])) {
			if (is_bool($val))
				$this->typeofmode[$mode] = self::type_normal;
			elseif (is_int($val))
				$this->typeofmode[$mode] = self::type_int;
			elseif (is_string($val))
				$this->typeofmode[$mode] = self::type_string;
			else
				$this->typeofmode[$mode] = self::type_unknown;
		}
		switch ($this->typeofmode[$mode]) {
			case self::type_string:
				$this->_values[$mode] = (string)$val;
				break;
			case self::type_normal:
				if (!is_bool($val))
					throw new Exception("Invalid boolean for mode $mode of {$this->parent->kind} {$this->parent->name}",008);
				$this->_values[$mode] = $val;
				break;
			case self::type_int:
				if (!is_numeric($val))
					throw new Exception("Invalid integer for mode $mode of {$this->parent->kind} {$this->parent->name}",009);
				$this->_values[$mode] = (int)$val;
				break;
			case self::type_array:
				if (!preg_match('/^\d\d?\d?:\d\d?\d?$/',$val))
					throw new Exception("Invalid array for mode $mode of {$this->parent->kind} {$this->parent->name}",010);
				$this->_values[$mode] = $val;
				break;
			case self::type_channel:
				if (!channel::valid_name($val))
					throw new Exception("Invalid channel for mode $mode of {$this->parent->kind} {$this->parent->name}",011);
				$this->_values[$mode] = $val;
				break;
			case self::type_user:
				if (!$val instanceof mode_type_user_action)
					throw new Exception("Invalid mode_type_user_action for mode $mode of {$this->parent->kind} {$this->parent->name}",012);
				if ($val->add)
					$this->_values[$mode]['by_name'][strtolower($val->user->name)] =& $val->user;
				else
					unset($this->_values[$mode]['by_name'][strtolower($val->user->name)]);
				break;
			case self::type_wild:
				if (!$val instanceof mode_type_wild_action)
					throw new Exception("Invalid mode_type_wild_action for mode $mode of {$this->parent->kind} {$this->parent->name}",013);
				if ($val->add) {
					$this->_values[$mode]['by_wild'][strtolower($val->wild)] = $val->wild;
				}
				else {
					unset($this->_values[$mode]['by_wild'][strtolower($val->wild)]);
				}
				//TODO more indexing!
				break;
			case self::type_flood:
				if (!$val)
					$this->_values[$mode] = '';
				else {
					if (!preg_match('/^\[[0-9]{1,3}[cjkmnt](#[CmMiRKNb][0-9]{1,3})?(,[0-9]{1,3}[cjkmnt](#[CmMiRKNb][0-9]{1,3})?)*\]:[0-9]{1-3}$/',$val))
					if (false)
						throw new Exception("Invalid flood for mode $mode of {$this->parent->kind} {$this->parent->name}",014);
					$this->_values[$mode] = $val;
				}
				break;
			case self::type_unknown:
				//TODO add serializer
				throw new Exception("Invalid unknown mode $mode of {$this->parent->kind} {$this->parent->name}",015);
				break;
		}
		return true;
	}
	public function __toString() {
		$modes = "";
		$modespars = array();
		for ($j = 0; $j < strlen($this->_modes); $j++) {
			$mode = $modes{$j};
			if (isset($this->_values[$mode])) {
				$type = $this->typeofmode[$mode];
				if ($type == self::type_user and !empty($this->_values[$mode]['by_name']))
					foreach ($this->_values[$mode]['by_name'] as &$user) {
						$modes .= $mode;
						$modespars[] = $user->name;
					}
				elseif ($type == self::type_wild and !empty($this->_values[$mode]['by_wild']))
					foreach ($this->_values[$mode]['by_wild'] as $wild) {
						$modes .= $mode;
						$modespars[] = $wild;
					}
				elseif ($this->has_parameter($mode)) {
					$modespars[] = $this->_values[$mode];
				}
				$modes .= $mode;
			}
		}
		return $modes.' '.implode(' ',$modespars);
	}
}
function modes_apply (modes &$m, $apply, $serversyntax = false) {
	$parameters = '';
	$modes = firstword($apply, $parameters);
	$parameters = explode(' ',$parameters);
	$diff = '';
	$diffplus = true;
	$diffpar = array();
	$add = true;
	for ($i = 0; $i < strlen($modes); $i++) {
		$mo = $modes{$i};
		if ($mo == '+' or $mo == '-') {
			$add = $mo == '+';
			continue;
		}
		$mc = ($mo == strtolower($mo) ? 'l' : 'u') . $mo;
		if ($diffplus != $add) $diff = rtrim($diff,'+-').($add ? '+' : '-');
		if (!array_key_exists($mo,$m->typeofmode))
			$m->$mc = $add;
		else switch ($m->typeofmode[$mo]) {
			case modes::type_int:
				$new = ($add ? (int) current($parameters) : 0);
				if ($m->$mc != $new) {
					$diff .= $mo;
					if ($m->has_parameter($mo, $add))
						$diffpar[] = $new; // i assume here there is no !$add parameter ever
				}
				$m->$mc = $new;
				break;
			case modes::type_normal:
				if ($m->$mc != $add) {
					$diff .= $mo;
					//again i assume this doesn't have params
				}
				$m->$mc = $add;
				break;
			case modes::type_array:
			case modes::type_channel:
			case modes::type_string:
				$new = ($add ? current($parameters) : false);
				if ($m->$mc != $new) {
					$diff .= $mo;
					if ($m->has_parameter($mo, $add))
						$diffpar[] = ((!$add and !$serversyntax) ? $this->$mc : $new);
				}
				$m->$mc = $new;
				break;
			case modes::type_flood:
				$par = ((!$serversyntax and !$add) ? $this->$mc : current($parameters));
				if (!$par) continue; //trying to unset inexistant +f; ignoring
				$new = ($add ? $par : false);
				if ($m->$mc != $new) {
					$diff .= $mo;
					$diffpar[] = $par; //assuming flood always has parameter
				}
				$m->$mc = $new;
				break;
			case modes::type_user:
				$user = current($parameters);
				global $irc;
				if (!$irc instanceof protocol_module) 
					throw new Exception("Couldn't locat main protocol module for make_basic");
				if (!$irc->make_basic($user))
					throw new Exception("User ".current($parameters)." not found in repository");
				if (!$user instanceof user)
					throw new Exception("Expected parameter ".current($parameters)." to turn out a user");
				if ($m->$mc = new mode_type_user_action($user, $add)) {
					$diff .= $mo;
					$diffpar[] = $user->name; //assuming user always has parameter
				}
				break;
			case modes::type_wild:
				if ($m->$mc = new mode_type_wild_action(current($parameters), $add)) {
					$diff .= $mo;
					$diffpar[] = current($parameters); //assuming wild always has parameter
				}
				break;
			default:
		}
		if ($m->has_parameter($mo, $add, $serversyntax))
			next($parameters);
	}
	$diffpar[-1] = rtrim($diff,'+-');
	return implode(' ',$diffpar);
}
?>
